/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import java.util.*;
import java.util.stream.Collectors;

class StanfordCryptoPbkdf2 {
    public static final int PBKDF2_NUM_2 = 2;
    public static final int PBKDF2_NUM_3 = 3;
    public static final int PBKDF2_NUM_4 = 4;
    public static final int PBKDF2_NUM_5 = 5;
    public static final int PBKDF2_NUM_6 = 6;
    public static final int PBKDF2_NUM_7 = 7;
    public static final int PBKDF2_NUM_8 = 8;
    public static final int PBKDF2_NUM_9 = 9;
    public static final int PBKDF2_NUM_10 = 10;
    public static final int PBKDF2_NUM_11 = 11;
    public static final int PBKDF2_NUM_12 = 12;
    public static final int PBKDF2_NUM_13 = 13;
    public static final int PBKDF2_NUM_14 = 14;
    public static final int PBKDF2_NUM_15 = 15;
    public static final int PBKDF2_NUM_16 = 16;
    public static final int PBKDF2_NUM_17 = 17;
    public static final int PBKDF2_NUM_18 = 18;
    public static final int PBKDF2_NUM_19 = 19;
    public static final int PBKDF2_NUM_25 = 25;
    public static final int PBKDF2_NUM_26 = 26;
    public static final int PBKDF2_NUM_21 = 21;
    public static final int PBKDF2_NUM_22 = 22;
    public static final int PBKDF2_NUM_30 = 30;
    public static final int PBKDF2_NUM_31 = 31;
    public static final int PBKDF2_NUM_32 = 32;
    public static final int PBKDF2_NUM_64 = 64;
    public static final int PBKDF2_NUM_120 = 20;
    public static final int PBKDF2_NUM_511 = 511;
    public static final int PBKDF2_NUM_512 = 512;
    public static final int PBKDF2_NUM_1000 = 1000;
    public static final int PBKDF2_NUM_10000 = 10000;
    public static final int PBKDF2_NUM_2147483647 = 2147483647;
    public static final long PBKDF2_NUM_NEGATIVE_2147483648 = 2147483648L;
    public static final long PBKDF2_NUM_9007199254740991 = 9007199254740991L;
    public static final long PBKDF2_HEX_10000000000 = 0x10000000000L;
    public static final int PBKDF2_HEX_80000000 = 0x80000000;
    public static final int PBKDF2_HEX_FFFFFFFF = 0xffffffff;
    public static final long PBKDF2_HEX_F00000000000 = 0xf00000000000L;
    public static final long PBKDF2_HEX_100000000 = 0x100000000L;
    public static final int PBKDF2_HEX_36363636 = 0x36363636;
    public static final int PBKDF2_HEX_5C5C5C5C = 0x5c5c5c5c;
 
    public static long initUInt32(long a) {
         long tmp = a;
         if (tmp > PBKDF2_NUM_2147483647) {
             long max = 4294967295L;
             tmp = tmp % (max + 1);
             if (tmp > PBKDF2_NUM_2147483647) {
                 tmp = tmp - max - 1;
             }
         } else if (tmp < -PBKDF2_NUM_NEGATIVE_2147483648) {
             long max = 4294967295L;
             tmp = tmp % (max + 1);
             if (tmp < -PBKDF2_NUM_NEGATIVE_2147483648) {
                 tmp = tmp + max + 1;
             }
         }
         return tmp;
    }
 
    public static void debugLog(String str, Object object) {
        boolean isLog = true;
        if (isLog) {
            System.out.println(str + object.toString());
        }
    }
 
     public static long getTime() {
        return System.nanoTime();
     }


    class BitArray {
        public static long partial(long len, long x) {
            return partial(len, x, false);
        }
        /**
         * Make a partial word for a bit array.
                  * @param {Number} len The number of bits in the word.
         * @param {Number} x The bits.
         * @param {Number} [end=0] Pass 1 if x has already been shifted to the high side.
         * @return {Number} The partial word.
         */
        public static long partial(long len, long x, boolean end) {
            if (len == PBKDF2_NUM_32) {
                return x;
            } else {
                long result = (end ? x | 0 : initUInt32(x << (PBKDF2_NUM_32 - len))) + len * PBKDF2_HEX_100000000;
                return result;
            }
        }
    
        /**
         * Concatenate two bit arrays.
         * @param {bitArray} a1 The first array.
         * @param {bitArray} a2 The second array.
         * @return {bitArray} The concatenation of a1 and a2.
         */
        public static ArrayList<Long> concat(ArrayList<Long> a1, ArrayList<Long> a2) {
            if (a1.size() == 0 || a2.size() == 0) {
                a1.addAll(a2);
                return a1;
            }
    
            long last = a1.get(a1.size()-1);
            long shift = BitArray.getPartial(last);
            if (shift == PBKDF2_NUM_32) {
                a1.addAll(a2);
                return a1;
            } else {
                return BitArray.shiftRight(a2, shift, last | 0, (ArrayList<Long>) a1.subList(0, a1.size() - 1));
            }
        }
        /**
         * Truncate an array.
         * @param {bitArray} aa The array.
         * @param {Number}   len1 The length to truncate to, in bits.
         * @return {bitArray} A new array, truncated to len bits.
         */
        public static ArrayList<Long> clamp(ArrayList<Long> aa, int len1) {
            ArrayList<Long> a = aa;
            int len = len1;
            if (a.size() * PBKDF2_NUM_32 < len) {
                return a;
            }
    
            int sliceLength = (int) Math.ceil(len / PBKDF2_NUM_32);
            a = new ArrayList<>(a.subList(0, sliceLength));
    
            int l = a.size();
            len = len & PBKDF2_NUM_31;
    
            if (l > 0 && len != 0) {
                a.set(l - 1, BitArray.partial(len, a.get(l - 1) & (PBKDF2_HEX_80000000 >> (len - 1)), true));
            }
    
            return a;
        }
    
        /**
         * Find the length of an array of bits.
         * @param {bitArray} a The array.
         * @return {Number} The length of a, in bits.
         */
        public static int bitLength(ArrayList<Long> a) {
            int l = a.size();
            long x;
            if (l == 0) {
                return 0;
            }
            x = a.get(l - 1);
            return (int) ((l - 1) * PBKDF2_NUM_32 + BitArray.getPartial(x));
        }
    
        /**
         * Get the number of bits used by a partial word.
         * @param {Number} x The partial word.
         * @return {Number} The number of bits used by the partial word.
         */
        public static long getPartial(long x) {
            return Math.round((float) x / PBKDF2_HEX_10000000000) != 0 ? Math.round((float) x / PBKDF2_HEX_10000000000) : PBKDF2_NUM_32;
        }
        /**
         * Shift an array right.
         * @param {bitArray} a The array to shift.
         * @param {Number}   shift1 The number of bits to shift.
         * @param {Number}   [carry1=0] A byte to carry in
         * @param {bitArray} [out1=[]] An array to prepend to the output.
         * @private
         */
        public static ArrayList<Long> shiftRight(ArrayList<Long> a, long shift1, long carry1, ArrayList<Long> out1) {
            long last2 = 0;
            long shift2;
            ArrayList<Long> out = out1;
            long carry = initUInt32(carry1);
            long shift = shift1;
            if (out.size() == 0) {
                out.removeAll(out);
            }
    
            while (shift >= PBKDF2_NUM_32) {
                out.add(carry);
                carry = 0;
                shift -= PBKDF2_NUM_32;
            }
    
            if (shift == 0) {
                out.addAll(a);
                return out;
            }
    
            for (int i = 0; i < a.size(); i++) {
                out.add(carry | ((int) initUInt32(a.get(i)) >>> shift));
                carry = (a.get(i) << (PBKDF2_NUM_32 - shift)) & PBKDF2_HEX_FFFFFFFF;
            }
    
            last2 = a.size() == 0 ? 0 : a.get(a.size() - 1);
            shift2 = BitArray.getPartial(last2) != 0 ? 1 : 0;
            out.add(BitArray.partial((shift + shift2) & PBKDF2_NUM_31, shift + shift2 > PBKDF2_NUM_32 ? carry : out.remove(out.size()-1), true));
            return out;
        }
    }
    /**
     * UTF-8 strings
     * @namespace
     */
    class Utf8String {
        public static ArrayList<Long> toBits(String str) {
            ArrayList<Long> out = new ArrayList<>();
            int i = 0;
            long tmp = 0;
            while (i < str.length()) {
                tmp = (tmp << PBKDF2_NUM_8) | (int) str.charAt(i);
                if ((i & PBKDF2_NUM_3) == PBKDF2_NUM_3) {
                    out.add(tmp);
                    tmp = 0;
                }
                i += 1;
            }
            if ((i & PBKDF2_NUM_3) != 0) {
                out.add(BitArray.partial(PBKDF2_NUM_8 * (i & PBKDF2_NUM_3), tmp));
            }
            return out;
        }
    }
    
    /**
     * Hexadecimal
     * @namespace
     */
    class SjclCodecHex {
        /** Convert from a bitArray to a hex string. */
        public static String fromBits(ArrayList<Long> arr) {
            String out = "";
            for (int i = 0; i < arr.size(); i++) {
                long hexValue = (arr.get(i) | 0) + PBKDF2_HEX_F00000000000;
                out += Long.toHexString(hexValue).substring(PBKDF2_NUM_4);
            }
            String str = out.substring(0, BitArray.bitLength(arr) / PBKDF2_NUM_4);
            return str;
        }
        /** Convert from a hex string to a bitArray. */
        public static ArrayList<Long> toBits(String str) {
            ArrayList<Long> out = new ArrayList<>();
            String strHex = str.replaceAll("\\s|0x", "");
            int len = strHex.length();
            strHex = strHex + "00000000";
    
            int i = 0;
            while (i < strHex.length()) {
                int hexValue = hexSwitch(strHex.substring(i, i + PBKDF2_NUM_8));
                out.add(initUInt32(hexValue) ^ 0);
                i += PBKDF2_NUM_8;
            }
    
            return BitArray.clamp(out, len * PBKDF2_NUM_4);
        }
    }
    
        public static int hexSwitch(String hexStr) {
            int i = 0;
            int sum = 0;
            while (i < hexStr.length()) {
                int s = hexStr.charAt(i);
                int n = 0;
                switch (s) {
                    case 'A':
                        n = PBKDF2_NUM_10;
                        break;
                    case 'B':
                        n = PBKDF2_NUM_11;
                        break;
                    case 'C':
                        n = PBKDF2_NUM_12;
                        break;
                    case 'D':
                        n = PBKDF2_NUM_13;
                        break;
                    case 'E':
                        n = PBKDF2_NUM_14;
                        break;
                    case 'F':
                        n = PBKDF2_NUM_15;
                        break;
                    default:
                        n = n;
                        break;
                }
                sum = sum + n * (int) Math.pow(PBKDF2_NUM_16, hexStr.length() - i - 1);
                i += 1;
            }
            return sum;
        }
    
    /** @fileOverview Javascript SHA-256 implementation.
     *
     * An older version of this implementation is available in the public
     * domain, but this one is (c) Emily Stark, Mike Hamburg, Dan Boneh,
     * Stanford University 2008-2010 and BSD-licensed for liability
     * reasons.
     *
     * Special thanks to Aldo Cortesi for pointing out several bugs in
     * this code.
     *
     * @author Emily Stark
     * @author Mike Hamburg
     * @author Dan Boneh
     */
    static class Sha256 {
        /**
         * The SHA-256 initialization vector, to be precomputed.
         * @private
         */
        public long[] init = {};
        /*
         * init:[0x6a09e667,0xbb67ae85,0x3c6ef372,0xa54ff53a,0x510e527f,0x9b05688c, 0x1f83d9ab,0x5be0cd19],
         */
    
        /**
         * The SHA-256 hash key, to be precomputed.
         * @private
         */
        public long[] key = {};
        /*
         * key:
         * [0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
         * 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
         * 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
         * 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
         * 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb,  0x81c2c92e, 0x92722c85,
         * 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
         * 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
         * 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb,  0xbef9a3f7, 0xc67178f2],
         */
    
        /**
         * The hash's block size, in bits.
                  * @constant
         */
        public int blockSize = PBKDF2_NUM_512;
    
        public long[] h = {};
    
        public ArrayList<Long> buffer = new ArrayList<>();
    
        public int length = 0;
    
        /**
         * Hash a string or an array of words.
         * @static
         * @param {bitArray|String} data the data to hash.
         * @return {bitArray} The hash value, an array of 16 big-endian words.
         */
        public static ArrayList<Long> hash(Object data) {
            return new Sha256(null).update(data).finalizes();
        }
        /**
         * Context for a SHA-256 operation in progress.
         * @constructor
         */
        Sha256(Sha256 hash) {
            if (this.key.length == 0) {
                this.precompute();
            }
            if (hash != null) {
                this.h = Arrays.copyOf(hash.h, hash.h.length);
                this.buffer = hash.buffer;
                this.length = hash.length;
            } else {
                this.reset();
            }
        }
        public int frac(double x) {
            long a = (long) ((x - Math.floor(x)) * PBKDF2_HEX_100000000);
            a = initUInt32(a);
            return (int) a;
        }
        /**
         * Function to precompute init and key.
         * @private
         */
        private void precompute() {
            int i = 0;
            int prime = 2;
            boolean isPrime;
            this.init = java.util.stream.LongStream.generate(() -> 0).limit(PBKDF2_NUM_8).toArray();
            this.key = java.util.stream.LongStream.generate(() -> 0).limit(PBKDF2_NUM_64).toArray();
            while (i < PBKDF2_NUM_64) {
                isPrime = true;
                int factor = PBKDF2_NUM_2;
                while (factor * factor <= prime) {
                    if (prime % factor == 0) {
                        isPrime = false;
                        break;
                    }
                    factor += 1;
                }
                if (isPrime) {
                    if (i < PBKDF2_NUM_8) {
                        this.init[i] = this.frac(Math.pow(prime, (double) 1 / PBKDF2_NUM_2));
                    }
                    this.key[i] = this.frac(Math.pow(prime, (double) 1 / PBKDF2_NUM_3));
                    i += 1;
                }
                prime += 1;
            }
        }
        /**
         * Reset the hash state.
         * @return this
         */
        public Sha256 reset() {
            this.h = this.init;
            this.buffer = new ArrayList<>();
            this.length = 0;
            return this;
        }
        /**
         * Input several words to the hash.
         * @param {bitArray|String} data the data to hash.
         * @return this
         */
        public Sha256 update(Object datas) {
            ArrayList<Long> data;
            if (datas instanceof String) {
                data = Utf8String.toBits((String) datas);
            } else {
                data = (ArrayList<Long>) datas;
            }
            int i = 0;
            this.buffer = BitArray.concat(this.buffer, data);
            ArrayList<Long> b = this.buffer;
            int ol = this.length;
            this.length = this.length + BitArray.bitLength(data);
            int nl = this.length;
            if (nl > PBKDF2_NUM_9007199254740991) {
                throw new Error("INVALID Cannot hash more than 2^53 - 1 bits");
            }
            ArrayList<Long> c = b.stream().map(StanfordCryptoPbkdf2::initUInt32).collect(Collectors.toCollection(ArrayList::new));
            int j = 0;
            i = PBKDF2_NUM_512 + ol - ((PBKDF2_NUM_512 + ol) & PBKDF2_NUM_511);
            while (i <= nl) {
                this.block( new ArrayList<>(c.subList(PBKDF2_NUM_16 * j, PBKDF2_NUM_16 * (j + 1))));
                j += 1;
                i += PBKDF2_NUM_512;
            }
            b.removeAll(b.subList(0, PBKDF2_NUM_16 * j));
            return this;
        }
            /**
         * Complete hashing and output the hash value.
                  * @return {bitArray} The hash value, an array of 8 big-endian words.
         */
        public ArrayList<Long> finalizes() {
            // Round out and push the buffer
            this.buffer = BitArray.concat(this.buffer, new ArrayList<>(Collections.nCopies(1, BitArray.partial(1, 1, false))));
    
            // Round out the buffer to a multiple of 16 words, less the 2 length words.
            int i = this.buffer.size() + PBKDF2_NUM_2;
            while ((i & PBKDF2_NUM_15) != 0) {
                this.buffer.add(0L);
                i += 1;
            }
            // append the length
            long a = ((this.length / PBKDF2_HEX_100000000) | 0);
            this.buffer.add(a);
            this.buffer.add((long) this.length);
    
            while (!(this.buffer.size() == 0)) {
                ArrayList<Long> chunk = new ArrayList<>(this.buffer.subList(0, PBKDF2_NUM_16));
                this.buffer.removeAll(this.buffer.subList(0, PBKDF2_NUM_16));
                this.block(chunk);
            }
            this.reset();
            return Arrays.stream(this.h).boxed().collect(Collectors.toCollection(ArrayList::new));
        }
    
        /**
         * Perform one cycle of SHA-256.
                  * @param {Uint32Array|bitArray} w one block of words.
         * @private
         */
        private void block(ArrayList<Long> input) {
            ArrayList<Long> w = input;
            long tmp;
            int a;
            int b;
            long[] k = this.key;
            long h0 = this.h[0];
            long h1 = this.h[1];
            long h2 = this.h[PBKDF2_NUM_2];
            long h3 = this.h[PBKDF2_NUM_3];
            long h4 = this.h[PBKDF2_NUM_4];
            long h5 = this.h[PBKDF2_NUM_5];
            long h6 = this.h[PBKDF2_NUM_6];
            long h7 = this.h[PBKDF2_NUM_7];

    /* Rationale for placement of |0 :
     * If a value can overflow is original 32 bits by a factor of more than a few
     * million (2^23 ish), there is a possibility that it might overflow the
     * 53-bit mantissa and lose precision.
     *
     * To avoid this, we clamp back to 32 bits by |'ing with 0 on any value that
     * propagates around the loop, and on the hash state h[].  I don't believe
     * that the clamps on h4 and on h0 are strictly necessary, but it's close
     * (for h4 anyway), and better safe than sorry.
     *
     * The clamps on h[] are necessary for the output to be correct even in the
     * common case and for short inputs.
     */
            for (int i = 0; i < PBKDF2_NUM_64; i++) {
// load up the input word for this round
                if (i < PBKDF2_NUM_16) {
                    tmp = w.get(i);
                } else {
                    a = (int) initUInt32(w.get((i + 1) & PBKDF2_NUM_15));
                    b = (int) initUInt32(w.get((i + PBKDF2_NUM_14) & PBKDF2_NUM_15));
                    
                    long value1 = (a >>> PBKDF2_NUM_7) ^ (a >>> PBKDF2_NUM_18) ^ (a >>> PBKDF2_NUM_3) ^ (a << PBKDF2_NUM_25) ^ (a << PBKDF2_NUM_14);
                    long value2 = (b >>> PBKDF2_NUM_17) ^ (b >>> PBKDF2_NUM_19) ^ (b >>> PBKDF2_NUM_10) ^ (b << PBKDF2_NUM_15) ^ (b << PBKDF2_NUM_13);
                    long value3 = w.get(i & PBKDF2_NUM_15) + w.get((i + PBKDF2_NUM_9) & PBKDF2_NUM_15);

                    w.set(i & PBKDF2_NUM_15, initUInt32(value1 + value2 + value3));
                    tmp = w.get(i & PBKDF2_NUM_15);
                }
                h4 = initUInt32(h4);
                long value = 
					((int) h4 >>> PBKDF2_NUM_6) ^ ((int) h4 >>> PBKDF2_NUM_11) ^ ((int) h4 >>> PBKDF2_NUM_25) ^ (h4 << PBKDF2_NUM_26) ^ (h4 << PBKDF2_NUM_21) ^ (h4 << PBKDF2_NUM_7);
                tmp = initUInt32(tmp + h7 + value + (h6 ^ (h4 & (h5 ^ h6))) + k[i]);
                // shift register
                h7 = initUInt32(h6);
                h6 = initUInt32(h5);
                h5 = initUInt32(h4);
                h4 = initUInt32(h3 + tmp);
                h3 = initUInt32(h2);
                h2 = initUInt32(h1);
                h1 = initUInt32(h0);
                
                h0 = initUInt32(
					tmp + 
						((h1 & h2) ^ (h3 & (h1 ^ h2))) + 
						(((int) h1 >>> PBKDF2_NUM_2) ^ ((int) h1 >>> PBKDF2_NUM_13) ^ ((int) h1 >>> PBKDF2_NUM_22) ^ (h1 << PBKDF2_NUM_30) ^ (h1 << PBKDF2_NUM_19) ^ (h1 << PBKDF2_NUM_10))
				);
            }
            
            this.h[0] = initUInt32(this.h[0] + h0);
            this.h[1] = initUInt32(this.h[1] + h1);
            this.h[PBKDF2_NUM_2] = initUInt32(this.h[PBKDF2_NUM_2] + h2);
            this.h[PBKDF2_NUM_3] = initUInt32(this.h[PBKDF2_NUM_3] + h3);
            this.h[PBKDF2_NUM_4] = initUInt32(this.h[PBKDF2_NUM_4] + h4);
            this.h[PBKDF2_NUM_5] = initUInt32(this.h[PBKDF2_NUM_5] + h5);
            this.h[PBKDF2_NUM_6] = initUInt32(this.h[PBKDF2_NUM_6] + h6);
            this.h[PBKDF2_NUM_7] = initUInt32(this.h[PBKDF2_NUM_7] + h7);
            this.init = this.h;
        }
    }
    
    /** Hmac with the specified hash function.
 * @constructor
 * @param {bitArray} key the key for Hmac.
 * @param {Object} [Hash=sjcl.hash.sha256] The hash function to use.
 */

    static class Hmac {
        public Sha256 hash;
        public Sha256[] baseHash;
        public Sha256 resultHash;
        public long[][] exKey = { {}, {} };
        public Boolean updated = false;
    
        Hmac(ArrayList<Long> key, Sha256 hash) {
            this.hash = hash;
            this.baseHash = new Sha256[] { new Sha256(null), new Sha256(null) };
            int bs = hash.blockSize / PBKDF2_NUM_32;
            this.exKey[0] = java.util.stream.LongStream.generate(() -> 0).limit(bs).toArray();
            this.exKey[1] = java.util.stream.LongStream.generate(() -> 0).limit(bs).toArray();
            ArrayList<Long> _key = key;
            if (_key.size() > bs) {
                _key = Sha256.hash(_key);
            }
            for (int i = 0; i < bs; i++) {
                if (i < _key.size()) {
                    this.exKey[0][i] = _key.get(i) ^ PBKDF2_HEX_36363636;
                    this.exKey[1][i] = _key.get(i) ^ PBKDF2_HEX_5C5C5C5C;
                } else {
                    this.exKey[0][i] = 0 ^ PBKDF2_HEX_36363636;
                    this.exKey[1][i] = 0 ^ PBKDF2_HEX_5C5C5C5C;
                }
            }
            ArrayList<Long> a = new ArrayList<>();
            ArrayList<Long> b = new ArrayList<>();
            for (long item : this.exKey[0]) {
                a.add(item);
            }
            for (long item : this.exKey[1]) {
                b.add(item);
            }
            this.baseHash[0].update(a);
            this.baseHash[1].update(b);
            this.resultHash = this.baseHash[0];
        }
    
        public ArrayList<Long> encrypt(ArrayList<Long> data) {
            if (!this.updated) {
                this.update(data);
                return this.digest();
            } else {
                throw new Error("encrypt on already updated Hmac called!");
            }
        }
    
        public void reset() {
            this.resultHash = new Sha256(this.baseHash[0]);
            this.updated = false;
        }
    
        public void update(ArrayList<Long> data) {
            this.updated = true;
            this.resultHash.buffer = new ArrayList<>();
            this.resultHash.update(data);
        }
    
        public ArrayList<Long> digest() {
            ArrayList<Long> w = this.resultHash.finalizes();
    
            Sha256 b = new Sha256(this.baseHash[1]);
            b.buffer = new ArrayList<>();
            Sha256 c = b.update(w);
    
            ArrayList<Long> finalResult = c.finalizes();
            this.reset();
            return finalResult;
        }
    }
    
    class Pbkdf2 {
        public static ArrayList<Long> pbkdf2(Object password, Object salt, int count, Integer length, Hmac rrff) {
            if (length == null || count < 0) {
                throw new Error("nvalid params to pbkdf2");
            }
            ArrayList<Long> passwordArr = new ArrayList<>();
            ArrayList<Long> saltArr = new ArrayList<>();
            if (password instanceof String) {
                ArrayList<Long> arr = Utf8String.toBits((String) password);
                int i = 0;
                while (i < arr.size()) {
                    long n = arr.get(i);
                    passwordArr.add(initUInt32(n));
                    i += 1;
                }
            } else {
                long[] arr = (long[]) password;
                int i = 0;
                while (i < arr.length) {
                    long n = arr[i];
                    passwordArr.add(initUInt32(n));
                    i += 1;
                }
            }
            if (salt instanceof String) {
                ArrayList<Long> arr = Utf8String.toBits((String) salt);
                int i = 0;
                while (i < arr.size()) {
                    long n = arr.get(i);
                    saltArr.add(initUInt32(n));
                    i += 1;
                }
            } else {
                ArrayList<Long> arr = (ArrayList<Long>) salt;
                int i = 0;
                while (i < arr.size()) {
                    long n = arr.get(i);
                    saltArr.add(initUInt32(n));
                    i += 1;
                }
            }
    
            ArrayList<Long> u;
            ArrayList<Long> ui;
            ArrayList<Long> out = new ArrayList<>();
    
            long k = 1;
            while (PBKDF2_NUM_32 * out.size() < (length == 0 ? 1 : length)) {
                ArrayList<Long> tmp = new ArrayList<>();
                tmp.add(k);
                u = new Hmac(passwordArr, new Sha256(null)).encrypt(BitArray.concat(new ArrayList<>(saltArr.subList(0, saltArr.size())), tmp));
                ui = new Hmac(passwordArr, new Sha256(null)).encrypt(BitArray.concat(new ArrayList<>(saltArr.subList(0, saltArr.size())), tmp));
    
                for (int i = 1; i < count; i++) {
                    ui = new Hmac(passwordArr, new Sha256(null)).encrypt(ui);
                    for (int j = 0; j < ui.size(); j++) {
                        u.set(j, u.get(j) ^ ui.get(j));
                    }
                }

                out.addAll(u);
                k += 1;
            }
            if (length != 0) {
                out = BitArray.clamp(out, length);
            }
    //         debugLog("pbkdf2-out", out);
            return out;
        }
        }
 }
 /*
  * @State
  * @Tags Jetstream2
  */
class Benchmark {
    /*
    * @Benchmark
    */
    public static void runIteration() {
        double startTime = StanfordCryptoPbkdf2.getTime();
        for (int i = 0; i < StanfordCryptoPbkdf2.PBKDF2_NUM_120; i++) {
            String password = "b4db33f";
            String salt = "BA DB EE FB AD BE EF 00";
            int count = 2048;
            ArrayList<Long> strArr = StanfordCryptoPbkdf2.SjclCodecHex.toBits(salt);
            // debugLog("strArr", strArr);
            String output = StanfordCryptoPbkdf2.SjclCodecHex.fromBits(StanfordCryptoPbkdf2.Pbkdf2.pbkdf2(password, strArr, count, 0, null));
            // debugLog("output", output);
            if (!output.equals("5436c4c0d73e425a521f4a5a13e0fffe5cd957a823f435385fa0a75068a26803")) {
                throw new Error("Bad output");
            }
        }
        double endTime = StanfordCryptoPbkdf2.getTime();
        System.out.println("stanford-crypto-pbkdf2: ms = " + (endTime - startTime) / 1000000);
    }
    public static void main(String[] args) {
        runIteration();
    }
}

